package web

import (
	"fmt"
	"html/template"
	"image/color"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gorilla/sessions"
	"github.com/julienschmidt/httprouter"
	"github.com/steambap/captcha"
	"notabug.org/ropemann/imabon/src/entities/board"
	"notabug.org/ropemann/imabon/src/entities/post"
	"notabug.org/ropemann/imabon/src/entities/thread"
	"notabug.org/ropemann/imabon/src/routes/utils"
	"notabug.org/ropemann/imabon/src/service/boardService"
	"notabug.org/ropemann/imabon/src/service/postService"
	"notabug.org/ropemann/imabon/src/service/threadService"
)

// Template Literals
var TL = map[string]string{
	"index":  "index.html",
	"thread": "thread.html",
	"board":  "board.html",
}
var ProcessRequestImage = utils.ProcessRequestImage
var PersistThread = threadService.PersistThread
var CreatePost = postService.Create
var NewCaptcha = captcha.New

type RoutesBroker struct {
	templates        *template.Template
	boardsByCategory map[string][]board.Board // category -> []board
	siteName         string
	siteTitle        string
	store            *sessions.CookieStore
	testMode         bool
}

func NewRoutesBroker(templatePath string, testMode bool) *RoutesBroker {
	rb := RoutesBroker{testMode: testMode}
	rb.init(templatePath)
	return &rb
}

func RegisterWebRoutes(router *httprouter.Router, templatePath string, testMode bool) {
	rb := NewRoutesBroker(templatePath, testMode)

	router.GET("/", rb.Index)
	router.GET("/b/:board", rb.Board)
	router.GET("/b/:board/:thread", rb.Thread)
	router.GET("/generate-captcha", rb.GenerateCaptcha)

	router.POST("/b/:board", rb.CreateThread)
	router.POST("/b/:board/:thread", rb.CreatePost)

	router.ServeFiles("/static/css/*filepath", http.Dir("public/css"))
}

func (rb *RoutesBroker) init(templatePath string) {
	rb.store = sessions.NewCookieStore([]byte(os.Getenv("SITE_SECRET")))

	boards := boardService.GetAllBoards()
	rb.boardsByCategory = make(map[string][]board.Board)
	for _, row := range boards {
		if len(rb.boardsByCategory[row.Category]) == 0 {
			rb.boardsByCategory[row.Category] = []board.Board{}
		}
		rb.boardsByCategory[row.Category] = append(rb.boardsByCategory[row.Category], row)
	}

	rb.siteName, rb.siteTitle = utils.SetSiteEnvs()
	// TODO: maybe add templates path to .env
	rb.templates = utils.ProcessTemplates(templatePath)
}

func (rb *RoutesBroker) verifyCaptcha(req *http.Request) bool {
	if rb.testMode {
		return true
	}
	session, _ := rb.store.Get(req, "session-name")
	captcha := fmt.Sprintf("%v", session.Values["captcha"])
	captchaTest := req.FormValue("captchaTest")
	return strings.Compare(captcha, captchaTest) == 0
}

func (rb *RoutesBroker) Index(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	recentThreads := threadService.GetMostRecentThreads()

	data := map[string]any{
		"siteTitle":        rb.siteTitle,
		"siteName":         rb.siteName,
		"boardsByCategory": rb.boardsByCategory,
		"recent":           recentThreads,
	}

	err := rb.templates.ExecuteTemplate(w, TL["index"], data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (rb *RoutesBroker) Board(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	threads := threadService.GetAllThreadsByBoard(ps.ByName("board"))

	data := map[string]any{
		"siteTitle":   rb.siteTitle,
		"board":       ps.ByName("board"),
		"threads":     threads,
		"postFailed":  req.URL.Query().Get("p") == "f",
		"postSuccess": req.URL.Query().Get("p") == "t",
	}

	err := rb.templates.ExecuteTemplate(w, TL["board"], data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (rb *RoutesBroker) Thread(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	thread := threadService.GetThreadById(ps.ByName("board"), ps.ByName("thread"))

	data := map[string]any{
		"siteTitle":   rb.siteTitle,
		"board":       ps.ByName("board"),
		"thread":      thread,
		"postFailed":  req.URL.Query().Get("p") == "f",
		"postSuccess": req.URL.Query().Get("p") == "t",
	}

	err := rb.templates.ExecuteTemplate(w, TL["thread"], data)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func (rb *RoutesBroker) CreateThread(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	if !rb.verifyCaptcha(req) {
		http.Redirect(
			w,
			req,
			fmt.Sprintf("/b/%s?p=f", ps.ByName("board")),
			http.StatusFound)
		return
	}

	imgBase64Str, _ := ProcessRequestImage(req)
	thread := thread.NewThread(
		req.FormValue("threadTitle"),
		req.FormValue("authorName"),
		req.FormValue("comment"),
		imgBase64Str)

	threadId, err := PersistThread(ps.ByName("board"), *thread)
	if err != nil {
		log.Println("Error occured on thread creation: ", err)
		http.Redirect(
			w,
			req,
			fmt.Sprintf("/b/%s", ps.ByName("board")),
			http.StatusInternalServerError)
		return
	}

	http.Redirect(
		w,
		req,
		fmt.Sprintf("/b/%s/%s?p=t", ps.ByName("board"), threadId),
		http.StatusFound)
}

func (rb *RoutesBroker) CreatePost(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	if !rb.verifyCaptcha(req) {
		http.Redirect(
			w,
			req,
			fmt.Sprintf("/b/%s/%s?p=f", ps.ByName("board"), ps.ByName("thread")),
			http.StatusFound)
		return
	}

	imgBase64Str, _ := ProcessRequestImage(req)
	post := post.NewPost(req.FormValue("authorName"), req.FormValue("comment"), imgBase64Str)

	affected, err := CreatePost(ps.ByName("board"), ps.ByName("thread"), *post)
	if err != nil || affected == 0 {
		log.Println("Error occured on post: ", err)
		http.Redirect(
			w,
			req,
			fmt.Sprintf("/b/%s/%s?p=f", ps.ByName("board"), ps.ByName("thread")),
			http.StatusInternalServerError)
		return
	}

	http.Redirect(
		w,
		req,
		fmt.Sprintf("/b/%s/%s?p=t", ps.ByName("board"), ps.ByName("thread")),
		http.StatusFound)
}

func (rb *RoutesBroker) GenerateCaptcha(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	img, _ := NewCaptcha(150, 50, func(options *captcha.Options) {
		options.BackgroundColor = color.White
	})

	session, _ := rb.store.Get(req, "session-name")
	session.Values["captcha"] = img.Text
	session.Save(req, w)

	img.WriteImage(w)
}
